﻿using System;
using System.Collections.Generic;
using Microsoft.Win32;

namespace BMproductions.Media.MediaPlayer.WMPPlus {
	public interface IPersistableModule {
		void Update();
	}

	public class WMPPlusSettings: IDisposable {
		#region Private fields
		public enum BoolNames
		{
			MSNEnable,
			MSNHideOnPause,
			MSNShowVideo,
			DisableGroupHeaders,
			FindAsYouType,
			IncludeSetInTrackNumbers,
			ShowATEInPlaylistView,
			CurrentMediaInTitle,
			ShowFileMenuItemLibraryPane,
			ShowFileMenuItemListPane,
			ShowTagEditorPlusLibraryPane,
			ShowTagEditorPlusListPane,
			ShowTagEditorPlusForOtherTypes,
			RestoreLastPlaylist,
			RestoreLastMedia,
			RestoreLastPlayState,
			RestoreLastPosition,
			RestorePaused,
			PartyShuffleConfigured,
			PartyShufflePendingSourceChange,
			SpaceBarPlayPause,
			Length
		};
		internal const WMPPlusProperty FirstBoolProperty = WMPPlusProperty.SettingsMSNEnable;
		public enum StringNames
		{
			MSNATFString,
			TaskbarATFString,
			PartyShufflePlaylistName,
			PartyShufflePlaylistFilename,
			PartyShuffleSourcePlaylistName,
			PartyShuffleSourcePlaylistFilename,
			Length
		}
		internal const WMPPlusProperty FirstStringProperty = WMPPlusProperty.SettingsMSNATFString;
		public enum NumberNames
		{
			LastUnsupportedVersion,
			TagEditorWidth,
			TagEditorHeight,
			TagEditorPage,
			MessageBoxWidth,
			MessageBoxHeight,
			RestorePositionRewind,
			PartyShufflePlayedSongs,
			PartyShuffleUpcomingSongs,
			PartyShufflePlaylistIndex,
			IncludeSetMaxItemCount,
			PlayNextMaxItemCount,
			Length
		}
		internal const WMPPlusProperty FirstNumberProperty = WMPPlusProperty.SettingsLastUnsupportedVersion;
		public static bool DefaultBoolValue = false;
		public static int DefaultNumberValue = 0;
		public static string[] DefaultTitleFormat =
		{
			"%title%[ - %artist%][ - %lengthf%][ - %year%]",
			"%title%[ - %artist%] - Windows Media Player",
			null, null, null, null
		};
		private enum PersistAction { Load, Save }

		ICollection<IPersistableModule> modules = new List<IPersistableModule>();
		private bool[] bools = new bool[(int)BoolNames.Length];
		public string[] strings = new string[(int)StringNames.Length];
		public int[] numbers = new int[(int)NumberNames.Length];

#endregion
		
		#region Methods
		public bool this[BoolNames key] {
			get { return bools[(int)key]; }
			set {
				if (bools[(int)key] != value) {
					bools[(int)key] = value;
					WMPPlusCore.SetBoolProp(FirstBoolProperty + (int)key, value);
				}
			}
		}
		public string this[StringNames key] {
			get { return strings[(int)key]; }
			set {
				if (strings[(int)key] != value) {
					strings[(int)key] = value;
					WMPPlusCore.SetStrProp(FirstStringProperty + (int)key, value);
				}
			}
		}
		public int this[NumberNames key] {
			get { return numbers[(int)key]; }
			set {
				if (numbers[(int)key] != value) {
					numbers[(int)key] = value;
					WMPPlusCore.SetIntProp(FirstNumberProperty + (int)key, value);
				}
			}
		}
		public int this[NumberNames key, int min, int max] {
			set {
				int val = Math.Max(min, value);
				if (max > 0)
					val = Math.Min(val, max);
				this[key] = val;
			}
		}

		public WMPPlusSettings() {
			Persist(PersistAction.Load);
		}

		public void Dispose() {
			this.modules.Clear();
		}

		private void PersistRegistry(RegistryKey key, string name, ref int value, int defValue, PersistAction action) {
			switch (action) {
			case PersistAction.Save:
				WMPPlusSettings.SaveRegistryNumber(key, name, value);
				break;
			case PersistAction.Load:
				value = WMPPlusSettings.LoadRegistryNumber(key, name, defValue);
				break;
			}
		}

		private void PersistRegistry(RegistryKey key, string name, ref string value, string defValue, PersistAction action) {
			switch (action) {
			case PersistAction.Save:
				WMPPlusSettings.SaveRegistryString(key, name, value);
				break;
			case PersistAction.Load:
				value = WMPPlusSettings.LoadRegistryString(key, name, defValue);
				break;
			}
		}

		private void PersistRegistry(RegistryKey key, string name, ref bool value, bool defValue, PersistAction action) {
			switch (action) {
			case PersistAction.Save:
				WMPPlusSettings.SaveRegistryBool(key, name, value);
				break;
			case PersistAction.Load:
				value = LoadRegistryBool(key, name, defValue);
				break;
			}
		}

		private void Persist(PersistAction action) {
			var root = Registry.CurrentUser;
			using (var key = action == PersistAction.Save ?
				root.CreateSubKey(WMPPlusCore.WMPPlusRegKey) :
				root.OpenSubKey(WMPPlusCore.WMPPlusRegKey, false)) {
				for(int k = 0; k < bools.Length; k++) {
					PersistRegistry(key, ((BoolNames)k).ToString(), ref bools[k], DefaultBoolValue, action);
				}
				for (int k = 0; k < numbers.Length; k++) {
					PersistRegistry(key, ((NumberNames)k).ToString(), ref numbers[k], DefaultNumberValue, action);
				}
				for (int k = 0; k < strings.Length; k++) {
					PersistRegistry(key, ((StringNames)k).ToString(), ref strings[k], DefaultTitleFormat[k], action);
				}
			}
			if (action == PersistAction.Load) {
				for (int k = 0; k < bools.Length; k++) {
					if (bools[k])
						WMPPlusCore.SetBoolProp(FirstBoolProperty + k, true);
				}
				for (int k = 0; k < numbers.Length; k++) {
					if (numbers[k] != 0)
						WMPPlusCore.SetIntProp(FirstNumberProperty + k, numbers[k]);
				}
				for (int k = 0; k < strings.Length; k++) {
					if (!strings[k].IsNullEmpty())
						WMPPlusCore.SetStrProp(FirstStringProperty + k, strings[k]);
				}
			}
		}

		public void SaveSettings() {
			Persist(PersistAction.Save);
		}

		public bool AddModule(IPersistableModule module) {
			if (this.modules.Contains(module))
				return false;
			this.modules.Add(module);
			return true;
		}

		public bool RemoveModule(IPersistableModule module) {
			return this.modules.Remove(module);
		}

		public void UpdateModules() {
			foreach (var module in this.modules)
				module.Update();
		}

		public static void SaveRegistryBool(RegistryKey key, string name, bool value) {
			SaveRegistryNumber(key, name, value ? 1 : 0);
		}

		public static bool LoadRegistryBool(RegistryKey key, string name, bool defValue) {
			if (key == null) return defValue;
			try {
				return (int)key.GetValue(name, defValue ? 1 : 0) == 1;
			}
			catch {
				return defValue;
			}
		}

		public static void SaveRegistryNumber(RegistryKey key, string name, int value) {
			if (key == null) return;
			try {
				var data = key.GetValue(name, null);
				var type = data == null ? RegistryValueKind.DWord : key.GetValueKind(name);
				switch (type) {
				case RegistryValueKind.String:
				case RegistryValueKind.ExpandString:
					key.SetValue(name, value.ToString(), type);
					break;
				case RegistryValueKind.DWord:
					key.SetValue(name, value, RegistryValueKind.DWord);
					break;
				default:
					data = data == null ? value : Convert.ChangeType(value, data.GetType());
					key.SetValue(name, data);
					break;
				}
			}
			catch {
				key.SetValue(name, value, RegistryValueKind.DWord);
			}
		}

		public static int LoadRegistryNumber(RegistryKey key, string name, int defValue) {
			if (key == null) return defValue;
			try {
				object value = key.GetValue(name);
				if (value == null) return defValue;
				switch (key.GetValueKind(name)) {
				case RegistryValueKind.String:
				case RegistryValueKind.ExpandString:
					int res;
					return int.TryParse(value.ToString(), out res) ? res : defValue;
				case RegistryValueKind.DWord:
					return (int)value;
				default:
					return (int)Convert.ChangeType(value, typeof(int));
				}
			}
			catch {
				return defValue;
			}
		}

		public static void SaveRegistryString(RegistryKey key, string name, string value) {
			if (key == null) return;
			try {
				var data = key.GetValue(name, null);
				var type = data == null ? RegistryValueKind.String : key.GetValueKind(name);
				switch (type) {
				case RegistryValueKind.String:
				case RegistryValueKind.ExpandString:
					key.SetValue(name, value.EmptyIfNull(), type);
					break;
				case RegistryValueKind.DWord:
					int num;
					key.SetValue(name, int.TryParse(value, out num) ? num : 0, RegistryValueKind.DWord);
					break;
				default:
					data = data == null ? value : Convert.ChangeType(value, data.GetType());
					key.SetValue(name, data ?? String.Empty);
					break;
				}
			}
			catch {
				key.SetValue(name, value.EmptyIfNull(), RegistryValueKind.String);
			}
		}

		public static string LoadRegistryString(RegistryKey key, string name, string defValue) {
			if (key == null) return defValue;
			try {
				object value = key.GetValue(name);
				if (value == null) return defValue;
				return value.ToString();
			}
			catch {
				return defValue;
			}
		}
		#endregion
	}
}
